﻿#pragma once

#include "../../thirdparty/kconfig/kconfig/interface/config.h"
#include <functional>
#include <memory>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <vector>
#include <ctime>

namespace kratos {
namespace config {

class BoxConfig;

/**
 * 配置重载事件回调函数原型
 */
using ReloadListener = std::function<void(const std::string&, const BoxConfig &)>;

/**
 * ServiceBox配置器.
 */
class BoxConfig {
public:
  virtual ~BoxConfig() {}
  /**
   * 添加加载事件监听器
   *
   * \param name 监听器名称
   * \param listener 回调函数
   * \retval true 成功
   * \retval false 失败
   */
  virtual auto add_reload_listener(const std::string &name,
                                   ReloadListener listener) -> bool = 0;
  /**
   * 删除新加载事件监听器.
   *
   * \param name 监听器名称
   * \retval true 成功
   * \retval false 失败
   */
  virtual auto remove_reload_listener(const std::string &name) -> bool = 0;
  /**
   * 获取配置器指针，用于获取自定义配置.
   *
   * \return 配置器指针
   */
  virtual auto get_config_ptr() -> kconfig::Config * = 0;
  /**
   * \brief 取得网络监听器列表, 例如:"127.0.0.1:12345"
   * \return 网络监听器列表
   */
  virtual auto get_listener_list() const
      -> const std::vector<std::string> & = 0;
  /**
   *
   * \return 服务发现类型
   */
  virtual auto get_service_finder_type() const -> const std::string & = 0;
  /**
   *
   *
   * \return 服务发现节点地址，例如："ip:port,domain:port"
   */
  virtual auto get_service_finder_hosts() const -> const std::string & = 0;
  /**
   *
   *
   * \return 连接服务发现节点超时时间，秒
   */
  virtual auto get_service_finder_connect_timeout() const ->std::time_t = 0;
  /**
   *
   *
   * \return 必需服务名列表
   */
  virtual auto get_necessary_service() const
      -> const std::vector<std::string> & = 0;
  /**
   *
   *
   * \return 连接其他服务容器的超时时间，秒
   */
  virtual auto get_connect_other_box_timeout() const -> int = 0;
  /**
   *
   *
   * \return 服务容器管道接收缓冲区长度，字节
   */
  virtual auto get_box_channel_recv_buffer_len() const -> int = 0;
  /**
   *
   *
   * \return 服务容器名称
   */
  virtual auto get_box_name() const -> const std::string & = 0;
  /**
   *
   *
   * \return 日志配置
   */
  virtual auto get_logger_config_line() const -> const std::string & = 0;
  /**
   *
   *
   * \return 本地服务存放目录
   */
  virtual auto get_service_dir() const -> const std::string & = 0;
  /**
   *
   *
   * \return 需要从本地预加载的服务名列表，<服务UUID，服务SO文件名>
   */
  virtual auto get_preload_service() const
      -> const std::unordered_map<std::string, std::string> & = 0;
  /**
   *
   *
   * \return 是否为服务提供方的每次调用开启一个协程， true或false
   */
  virtual auto is_open_coroutine() const -> bool = 0;
  /**
   *
   *
   * \return 远程服务版本获取API
   */
  virtual auto get_remote_service_repo_version_api() const
      -> const std::string & = 0;
  /**
   *
   *
   * \return 远程服务仓库地址
   */
  virtual auto get_remote_service_repo_dir() const -> const std::string & = 0;
  /**
   *
   *
   * \return 远程服务最新版本API
   */
  virtual auto get_remote_service_repo_latest_version_api() const
      -> const std::string & = 0;
  /**
   *
   *
   * \return 是否开启服务远程更新, true或false
   */
  virtual auto is_open_remote_update() const -> bool = 0;
  /**
   *
   *
   * \return 远程服务版本检查周期，秒
   */
  virtual auto get_remote_repo_check_interval() const -> int = 0;
  /**
   *
   *
   * \return 是否以守护进程的方式启动, true或false
   */
  virtual auto is_start_as_daemon() const -> bool = 0;
  /**
   *
   *
   * \return HTTP调用最长等待超时时间，秒
   */
  virtual auto get_http_max_call_timeout() const -> int = 0;
  /**
   *
   *
   * \return 是否开启RPC统计
   */
  virtual auto is_open_rpc_stat() -> bool = 0;
  /**
   * 检测属性是否存在.
   *
   * \param name 属性名
   * \return true或false
   */
  virtual auto has_attribute(const std::string &name) -> bool = 0;

  /**
   * 获取数组，转换为std::vector<T>,
   * T为基础类型，不支持复合类型的获取，复合类型获取需要使用kconfig::Config接口.
   *
   * \param name 属性名
   * \return std::vector<T>
   */
  template <typename T> inline
  auto get_array(const std::string &name) -> std::vector<T>;
  /**
   * 获取数组元素,
   * T为基础类型，不支持复合类型的获取，复合类型获取需要使用kconfig::Config接口
   *
   * \param name 属性名
   * \param index 数组索引
   * \return 数组元素
   */
  template <typename T> inline
  auto get_array(const std::string &name, int index) -> T;
  /**
   * 获取表数据，转换为std::unordered_map<std::string, T>,
   * T为基础类型，不支持复合类型的获取，复合类型获取需要使用kconfig::Config接口.
   *
   * \param name 属性名
   * \return std::unordered_map<std::string, T>
   */
  template <typename T> inline
  auto get_table(const std::string &name)
      -> std::unordered_map<std::string, T>;
  /**
   * 获取表内值,
   * T为基础类型，不支持复合类型的获取，复合类型获取需要使用kconfig::Config接口
   *
   * \param name 属性名
   * \param key 健值
   * \return 值
   */
  template <typename T> inline
  auto get_table(const std::string &name, const std::string &key) -> T;
  /**
   * 获取字符串.
   *
   * \param name 属性名
   * \return 字符串
   */
  auto get_string(const std::string &name) -> std::string;
  /**
   * 获取数字.
   *
   * \param name 属性名
   * \return 数字
   */
  template <typename T> inline auto get_number(const std::string &name) -> T;
  /**
   * 获取数字.
   *
   * \param attribute 属性
   * \return 数字
   */
  template <typename T> inline
  auto get_attr_number(kconfig::Attribute *attribute) -> T;
};

template <> inline
auto BoxConfig::get_array(const std::string &name)
    -> std::vector<std::string> {
  std::vector<std::string> result;
  auto *attribute = get_config_ptr()->get(name);
  if (!attribute->isArray()) {
    throw std::runtime_error("Attribute type is not array:" + name);
  }
  for (int i = 0; i < attribute->array()->getSize(); i++) {
    auto *element = attribute->array()->get(i);
    if (element->isString()) {
      result.push_back(element->string()->get());
    } else {
      throw std::runtime_error(
          "Array element type is only support: std::string, attribute name:" +
          name);
    }
  }
  return result;
}

template <typename T> inline
auto BoxConfig::get_array(const std::string &name) -> std::vector<T> {
  std::vector<T> result;
  auto *attribute = get_config_ptr()->get(name);
  if (!attribute->isArray()) {
    throw std::runtime_error("Attribute type is not array:" + name);
  }
  for (int i = 0; i < attribute->array()->getSize(); i++) {
    auto *element = attribute->array()->get(i);
    if (element->isNumber()) {
      result.push_back(get_attr_number<T>(element));
    } else {
      throw std::runtime_error(
          "Array element type is not support:" + std::string(typeid(T).name()) +
          ", attribute name:" + name);
    }
  }
  return result;
}

template <> inline
auto BoxConfig::get_array(const std::string &name, int index)
    -> std::string {
  auto *attribute = get_config_ptr()->get(name);
  if (!attribute->isArray()) {
    throw std::runtime_error("Attribute type is not array:" + name);
  }
  if (index >= attribute->array()->getSize()) {
    throw std::overflow_error("Array attribute index overflow:" + name);
  } else if (index < 0) {
    throw std::underflow_error("Array attribute index overflow:" + name);
  } else {
    auto *element = attribute->array()->get(index);
    if (element->isString()) {
      return element->string()->get();
    }
  }
  throw std::runtime_error(
      "Array element type is only support std::string, attribute name:" + name);
}

template <typename T> inline
auto BoxConfig::get_array(const std::string &name, int index) -> T {
  auto *attribute = get_config_ptr()->get(name);
  if (!attribute->isArray()) {
    throw std::runtime_error("Attribute type is not array:" + name);
  }
  if (index >= attribute->array()->getSize()) {
    throw std::overflow_error("Array attribute index overflow:" + name);
  } else if (index < 0) {
    throw std::underflow_error("Array attribute index overflow:" + name);
  } else {
    auto *element = attribute->array()->get(index);
    if (element->isString() && (typeid(std::string) == typeid(T))) {
      return element->string()->get();
    } else if (element->isNumber()) {
      return get_attr_number<T>(element);
    }
  }
  throw std::runtime_error(
      "Array element type is not support:" + std::string(typeid(T).name()) +
      ", attribute name:" + name);
}

template <typename T> inline
auto BoxConfig::get_table(const std::string &name)
    -> std::unordered_map<std::string, T> {
  std::unordered_map<std::string, T> dict;
  auto *attribute = get_config_ptr()->get(name);
  if (!attribute->isTable()) {
    throw std::runtime_error("Attribute type is not table:" + name);
  }
  auto *table = attribute->table();
  while (table->hasNext()) {
    auto *pair = table->next();
    if (pair->value()->isString() && (typeid(std::string) == typeid(T))) {
      dict.emplace(pair->key(), pair->value()->string()->get());
    } else if (pair->value()->isNumber()) {
      dict.emplace(pair->key(), get_attr_number<T>(pair->value()));
    } else {
      throw std::runtime_error(
          "Table value type is not support:" + std::string(typeid(T).name()) +
          ", attribute name:" + name);
    }
  }
  return dict;
}

template <> inline
auto BoxConfig::get_table(const std::string &name)
    -> std::unordered_map<std::string, std::string> {
  std::unordered_map<std::string, std::string> dict;
  auto *attribute = get_config_ptr()->get(name);
  if (!attribute->isTable()) {
    throw std::runtime_error("Attribute type is not table:" + name);
  }
  auto *table = attribute->table();
  while (table->hasNext()) {
    auto *pair = table->next();
    if (pair->value()->isString()) {
      dict.emplace(pair->key(), pair->value()->string()->get());
    } else {
      throw std::runtime_error(
          "Table value type is only support std::string, attribute name:" +
          name);
    }
  }
  return dict;
}

template <typename T> inline
auto BoxConfig::get_table(const std::string &name,
                                 const std::string &key) -> T {
  std::unordered_map<std::string, T> dict;
  auto *attribute = get_config_ptr()->get(name);
  if (!attribute->isTable()) {
    throw std::runtime_error("Attribute type is not table:" + name);
  }
  auto *table = attribute->table();
  auto *pair = table->get(key);
  if (!pair) {
    return std::runtime_error("Table attribute key not found, attribute name:" +
                              name + ", key:" + key);
  }
  auto *value = pair->value();
  if (value->isString() && (typeid(std::string) == typeid(T))) {
    return value->string()->get();
  } else if (value->isNumber()) {
    return get_attr_number<T>(value);
  }
  throw std::runtime_error(
      "Table value type is not support:" + std::string(typeid(T).name()) +
      ", attribute name:" + name);
}

template <typename T> inline auto BoxConfig::get_number(const std::string &name) -> T {
  auto *attribute = get_config_ptr()->get(name);
  if (!attribute->isNumber()) {
    throw std::runtime_error("Attribute type is not number:" + name);
  }
  return get_attr_number<T>(attribute);
}

template <> inline
auto BoxConfig::get_attr_number(kconfig::Attribute *attribute) -> std::uint8_t {
  return (std::uint8_t)attribute->number()->getUint();
}

template <> inline
auto BoxConfig::get_attr_number(kconfig::Attribute *attribute) -> std::uint16_t {
  return (std::uint16_t)attribute->number()->getUint();
}

template <> inline
auto BoxConfig::get_attr_number(kconfig::Attribute *attribute) -> std::uint32_t {
  return (std::uint32_t)attribute->number()->getUint();
}

template <> inline
auto BoxConfig::get_attr_number(kconfig::Attribute *attribute) -> std::uint64_t {
  return (std::uint64_t)attribute->number()->getULlong();
}

template <> inline
auto BoxConfig::get_attr_number(kconfig::Attribute *attribute) -> std::int8_t {
  return (std::int8_t)attribute->number()->getUint();
}

template <> inline
auto BoxConfig::get_attr_number(kconfig::Attribute *attribute) -> std::int16_t {
  return (std::int16_t)attribute->number()->getUint();
}

template <> inline
auto BoxConfig::get_attr_number(kconfig::Attribute *attribute) -> std::int32_t {
  return (std::int32_t)attribute->number()->getUint();
}

template <>
inline auto BoxConfig::get_attr_number(kconfig::Attribute *attribute) -> std::int64_t {
  return (std::int64_t)attribute->number()->getUint();
}

template <> inline
auto BoxConfig::get_attr_number(kconfig::Attribute *attribute) -> float {
  return attribute->number()->getFloat();
}

template <> inline
auto BoxConfig::get_attr_number(kconfig::Attribute *attribute) -> double {
  return attribute->number()->getDouble();
}

} // namespace config
} // namespace kratos
